using System;
using System.Collections.Generic;
using System.Reflection;
using FireStarter.Base;
using FireStarterModeller.BO;
using Habanero.Base;
using Habanero.Base.Exceptions;
using Habanero.BO.ClassDefinition;
using Habanero.BO.Loaders;

namespace FireStarterModeller.Logic
{
    public class DMToEntityClassDefsConverter : IConverter<DMClass, IClassDef>, IConverter<DMSolution, ClassDefCol>
    {
        private DMProperty _dmProperty;

        public ClassDefCol Convert(DMSolution solution)
        {
            //DMToEntityClassDefsXmlConverter toClassDefsXmlConverter = new DMToEntityClassDefsXmlConverter();
             ClassDefCol classes = new ClassDefCol();
            foreach (DMClass @class in solution.AllClasses)
            {
     
                ConvertOneClass(@class, classes);
            }
            //string xmlForSolution = toClassDefsXmlConverter.ConvertToXml(solution);
            //XmlClassDefsLoader loader = new XmlClassDefsLoader(xmlForSolution, new DtdLoader());
            //ClassDefCol classes = loader.LoadClassDefs();

            //foreach (ClassDef def in classes)
            //{
            //    UpdateClassForEntityStructure(def);
            //}
            return classes;
        }

        private void ConvertOneClass(DMClass @class, ClassDefCol classes)
        {
            if (@class.HasSuperClass)
            {
                ConvertOneClass(@class.SuperClass, classes);
            }

            IClassDef classDef = Convert(@class);
            if (classes.Contains(classDef.AssemblyName, classDef.ClassName)) return;
            classes.Add(classDef);
            if (ClassDef.ClassDefs.Contains(classDef.AssemblyName, classDef.ClassName))
            {
                ClassDef.ClassDefs.Remove(classDef.AssemblyName, classDef.ClassName);
            }
            ClassDef.ClassDefs.Add(classDef);
        }

        public IClassDef Convert(DMClass dmClass)
        {
            DMToEntityClassDefsXmlConverter toClassDefsXmlConverter = new DMToEntityClassDefsXmlConverter();
            dmClass.Save();
            ;
            try
            {
                EnsureDMClassHasUniqueConstraint(dmClass);
                string xmlForClass = toClassDefsXmlConverter.ConvertToXml(dmClass);

                IClassDef classDef = GetClassDef(xmlForClass);
                UpdateClassForEntityStructure(classDef);
                return classDef;
            }
            finally
            {
                dmClass.CancelEdits();
                if (dmClass.ObjectIdentity != null) dmClass.ObjectIdentity.CancelEdits();
            }
        }

        private static IClassDef GetClassDef(string xmlForClass)
        {
            var origionalExceptionNotifier = GlobalRegistry.UIExceptionNotifier;
            IClassDef classDef;
            try
            {
                var recordingExceptionNotifier = new RecordingExceptionNotifier();
                GlobalRegistry.UIExceptionNotifier = recordingExceptionNotifier;
                XmlClassLoader loader = new XmlClassLoader(new DtdLoader(), new DefClassFactory());
                classDef = loader.LoadClass(xmlForClass);
                recordingExceptionNotifier.RethrowRecordedException();
            }
            finally
            {
                GlobalRegistry.UIExceptionNotifier = origionalExceptionNotifier;
            }
            return classDef;
        }

        private void EnsureDMClassHasUniqueConstraint(DMClass @class) {
            if (@class.ObjectIdentity != null&& @class.ObjectIdentity.Properties.Count > 0) return;
            _dmProperty = @class.Properties.CreateBusinessObject();
            _dmProperty.PropertyName = "AutoID_" + Guid.NewGuid().ToString("N");
            _dmProperty.PropertyType = @class.Solution.GetDMPropertyType("System.String");
            DMObjectIdentity objectIdentity = @class.ObjectIdentity ?? @class.CreateObjectIdentity(_dmProperty);
            objectIdentity.IsObjectID = false;
        }

        /// <summary>
        /// The code below removes the Origional Primary Key and replaces it with the Primary Key from the Entity.
        /// The Entity's Primary Key is always a Guid EntityID.
        /// </summary>
        /// <param name="def"></param>
        private void UpdateClassForEntityStructure(IClassDef def) {
            IList<IPropDef> propDefs = new List<IPropDef>();
            PrimaryKeyDef primaryKeyDef = (PrimaryKeyDef) def.PrimaryKeyDef;
            if (primaryKeyDef != null)
            {
                foreach (IPropDef propDef in primaryKeyDef)
                {
                    propDefs.Add(propDef);
                    def.PropDefcol.Remove(propDef);
                }
                primaryKeyDef.Clear();
            } else
            {
                primaryKeyDef = new PrimaryKeyDef();
            }
            IClassDef entityClassDef = ClassDef.ClassDefs[typeof (Entity)];
            if (def.SuperClassDef == null)
            {
                foreach (IPropDef propDef in entityClassDef.PropDefcol)
                {
                    IPropDef propDef1 = propDef.Clone();
                    SetPropertyValue(propDef1, "ClassDef", def);
                    def.PropDefcol.Add(propDef1);
                }
                foreach (IPropDef propDef in entityClassDef.PrimaryKeyDef)
                {
                    primaryKeyDef.Add(propDef);
                }
                primaryKeyDef.IsGuidObjectID = true;

                KeyDef keyDef = new KeyDef("DomainPrimaryKey");
                foreach (IPropDef propDef in propDefs)
                {
                    def.PropDefcol.Add(propDef);
                    keyDef.Add(propDef);
                }
                def.KeysCol.Add(keyDef);
            }
        }


        ///<summary>
        /// Sets the value of a property of an object using reflection
        ///</summary>
        ///<param name="obj">The object for which to set the value</param>
        ///<param name="propertyName">The name of the property to be set</param>
        ///<param name="value">The value that is to be set</param>
        ///<exception cref="HabaneroArgumentException">This error is thrown when an invalid parameter is given</exception>
        ///<exception cref="TargetInvocationException">This error is thrown when there is an error in finding the property on the supplied object</exception>
        ///<exception cref="Exception">This is a general exception that is thrown if there is an error in retrieving the value.</exception>
        public static void SetPropertyValue(object obj, string propertyName, object value)
        {
            if (obj == null) throw new HabaneroArgumentException("obj");
            if (String.IsNullOrEmpty(propertyName)) throw new HabaneroArgumentException("propertyName");
            Type type = obj.GetType();
            string className = type.Name;
            try
            {
                PropertyInfo propInfo = GetPropertyInfo(type, propertyName);
                if (propInfo == null)
                {
                    throw new TargetInvocationException(new Exception(
                                                            String.Format("Virtual property set for '{0}' does not exist for object of type '{1}'.", propertyName, className)));
                }
                propInfo.SetValue(obj, value, new object[] { });
            }

            catch (TargetInvocationException ex)
            {

                //log.Error(String.Format("Error setting virtual property '{0}' for object of type '{1}'" +

                //Environment.NewLine + "{2}", propertyName, className,

                //ExceptionUtilities.GetExceptionString(ex.InnerException, 8, true)));

                throw ex.InnerException;

            }

        }

        /// <summary>
        /// Returns the PropertyInfo for a Private Property
        /// </summary>
        ///<param name="type">The type to find the specifed property on</param>
        ///<param name="propertyName">The name of the property to search for</param>
        ///<returns>The PropertyInfo object representing the requested method, or null if it does not exist.</returns>
        public static PropertyInfo GetPropertyInfo(Type type, string propertyName)
        {
            if (type == null) throw new HabaneroArgumentException("type");
            if (String.IsNullOrEmpty(propertyName)) throw new HabaneroArgumentException("propertyName");
            try
            {
                return type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty);
            }
            catch
            {
                return null;
            }
        }
 

    }


 
}